﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace Xceed.Silverlight.Data
{
  internal abstract class FetchPagingEnumerator<T> : PagingEnumerator<T>
  {
    #region Static Fields

    private static readonly Guid JumpMoveAndFetchCallId = DataUtils.CreateCallId();
    private static readonly Guid GetUnidirectionalPagesCallId = DataUtils.CreateCallId();
    private static readonly Guid GetBidirectionalPagesCallId = DataUtils.CreateCallId();
    private static readonly Guid GetBidirectionalReversedPagesCallId = DataUtils.CreateCallId();

    #endregion Static Fields

    #region Constructors

    protected FetchPagingEnumerator( IAsyncEnumerator childEnumerator )
      : base( childEnumerator )
    {
    }

    protected FetchPagingEnumerator( IAsyncEnumerator childEnumerator, Func<int> pageSize )
      : base( childEnumerator, pageSize )
    {
    }

    #endregion

    public override JumpMoveAndFetchAsyncResult BeginJumpMoveAndFetch(
      JumpMoveAndFetchParameters parameters,
      AsyncCallback callback,
      object state )
    {
      this.CheckMoveAndFetchParameters( parameters.ExpectedMoveCount, parameters.ExpectedFetchCount );

      JumpMoveAndFetchAsyncResult asyncResult = new JumpMoveAndFetchAsyncResult(
                                                  FetchPagingEnumerator<T>.JumpMoveAndFetchCallId,
                                                  callback,
                                                  state,
                                                  parameters );

      if( !this.HandleUnidirectionalPages( asyncResult, parameters ) )
        if( !this.HandleBidirectionalPages( asyncResult, parameters ) )
          throw new DataInternalException();

      return asyncResult;
    }

    public override JumpMoveAndFetchResult<T> EndJumpMoveAndFetch(
      JumpMoveAndFetchAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( FetchPagingEnumerator<T>.JumpMoveAndFetchCallId );

      return asyncResult.GetCallResult<JumpMoveAndFetchResult<T>>();
    }

    protected virtual Page GetNextPage(
      DataSourceContext context,
      SourcePosition sourcePosition,
      long moveCount,
      int moveUnit,
      int pageSize )
    {
      return new Page( sourcePosition, moveCount, 0 );
    }

    protected override void Dispose( bool disposing )
    {
      m_disposed = true;

      base.Dispose( disposing );
    }

    private JumpMoveAndFetchAsyncResult BeginGetUnidirectionalPages(
      JumpMoveAndFetchParameters parameters,
      AsyncCallback callback,
      object state )
    {
      long itemsCountToSkip = parameters.ExpectedMoveCount;
      int itemsCountToTake = parameters.ExpectedFetchCount;

      // Make sure the query is unidirectional.
      Debug.Assert( ( itemsCountToSkip == 0 )
                 || ( itemsCountToTake == 0 )
                 || ( DataUtils.IsQueryingForward( itemsCountToSkip, itemsCountToTake ) == DataUtils.IsQueryingForward( 0, itemsCountToTake ) ) );

      SourcePosition sourcePosition = parameters.SourcePosition;
      List<T> currentItems = new List<T>();
      Nullable<long> actualMoveCount = null;
      SourcePosition endPosition = null;
      RelativeOffset endPositionOffset = null;
      EstimateInt64 totalItemsCount = null;
      bool isFirstPage = true;
      int unit = ( itemsCountToSkip != 0 ) ? Math.Sign( itemsCountToSkip ) : Math.Sign( itemsCountToTake );

      // This anonymous method starts the query that will get a data page.
      Func<AsyncCallback, object, IAsyncResult> getPage = null;
      getPage =
        ( cb, st ) =>
        {
          long moveCount = 0;

          // It takes one move to get in bound.
          if( itemsCountToSkip != 0 && sourcePosition.IsOutOfBound )
          {
            moveCount = unit;
            itemsCountToSkip -= unit;
          }

          long itemsCount = itemsCountToSkip + itemsCountToTake;
          // Try to fetch an extra item to get an end position after
          // we've skip all items or to get over an item we have 
          // already fetched.
          if( ( itemsCountToSkip != 0 && itemsCountToTake == 0 )
            || ( itemsCountToSkip == 0 && itemsCountToTake == unit && sourcePosition is ItemPosition ) )
          {
            itemsCount += unit;
          }

          int pageSize = this.GetFetchCount( itemsCount );

          // Get the starting position of the next page.
          if( !isFirstPage )
          {
            Page nextPage = this.GetNextPage(
                              parameters.Context,
                              sourcePosition,
                              moveCount,
                              unit,
                              pageSize );

            sourcePosition = nextPage.SourcePosition;
            moveCount = nextPage.MoveCount;

            if( itemsCountToSkip != 0 )
            {
              long implicitMoveCount = nextPage.ImplicitMoveCount;
              if( implicitMoveCount != 0 )
              {
                itemsCountToSkip -= implicitMoveCount;

                if( actualMoveCount.HasValue )
                {
                  actualMoveCount = actualMoveCount.Value + implicitMoveCount;
                }
              }
            }
          }

          // Create the parameters to get the page.
          JumpMoveAndFetchParameters pageParameters = new JumpMoveAndFetchParameters(
                                                        sourcePosition,
                                                        moveCount,
                                                        pageSize,
                                                        parameters.ApproximateMove,
                                                        parameters.Context );

          return base.BeginJumpMoveAndFetch( pageParameters, cb, st );
        };

      // This anonymous method retrieve the data page and merge the result.
      Func<AsyncResult, IAsyncResult, DependentCallResult> mergePage = null;
      mergePage =
        ( par, iar ) =>
        {
          JumpMoveAndFetchResult<T> pageResult = base.EndJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )iar );

          if( ( m_disposed ) || ( pageResult == null ) )
            return new DependentCallResult( null );

          IEnumerable<T> fetchedItems = pageResult.FetchedItems;

          // Set the actual move count.
          if( isFirstPage )
          {
            actualMoveCount = pageResult.ActualMoveCount;

            ItemPosition startPosition = pageResult.SourcePosition as ItemPosition;

            // We must adjust the move count if the starting item position wasn't in the source.
            if( ( actualMoveCount == 0 )
              && ( itemsCountToSkip != 0 )
              && ( startPosition != null ) )
            {
              if( ( fetchedItems != null ) && ( fetchedItems.Any() ) && ( !object.Equals( startPosition.Item, fetchedItems.First() ) ) )
              {
                actualMoveCount = pageResult.GetFetchUnit();
                itemsCountToSkip -= Math.Sign( itemsCountToSkip );
              }
              else if( ( fetchedItems == null ) && ( pageResult.EndPositionObject.IsOutOfBound ) )
              {
                actualMoveCount = pageResult.GetFetchUnit();
              }
            }
          }

          // Set/Refresh the total items count.
          if( pageResult.TotalCount != null )
          {
            totalItemsCount = pageResult.TotalCount;
          }
          // The total items count we had previously is unreliable.
          else if( totalItemsCount != null )
          {
            totalItemsCount = totalItemsCount.ToEstimate();
          }

          if( fetchedItems != null )
          {
            int maxSkipCountAllowed = fetchedItems.Count() - ( ( pageResult.Result ) ? 1 : 0 );
            maxSkipCountAllowed = Math.Max( 0, maxSkipCountAllowed );

            // Skip the items that aren't in the final result.
            int maxSkipCount = Convert.ToInt32( Math.Min( maxSkipCountAllowed, Math.Abs( itemsCountToSkip ) ) );
            if( maxSkipCount > 0 )
            {
              int skipCount = maxSkipCount * unit;

              // Adjust the actual move count.
              if( actualMoveCount.HasValue )
              {
                actualMoveCount = actualMoveCount.Value + skipCount;
              }

              fetchedItems = fetchedItems.Skip( maxSkipCount );
              itemsCountToSkip -= skipCount;
            }

            // Take the items that are in the final result.
            if( ( itemsCountToSkip == 0 ) && ( fetchedItems.Any() ) )
            {
              // We must skip the starting item if we have already taken it last page.
              if( !isFirstPage && sourcePosition is ItemPosition )
              {
                if( object.Equals( fetchedItems.First(), ( ( ItemPosition )sourcePosition ).Item ) )
                {
                  fetchedItems = fetchedItems.Skip( 1 );
                }
              }

              int maxTakeCount = Math.Max( 0, Math.Min( fetchedItems.Count(), Math.Abs( itemsCountToTake ) ) );
              if( maxTakeCount > 0 )
              {
                currentItems.AddRange( fetchedItems.Take( maxTakeCount ) );

                fetchedItems = fetchedItems.Skip( maxTakeCount );
                itemsCountToTake -= maxTakeCount * unit;
              }
            }
          }

          // We do not need to fetch any more item since we've met the end of the source
          // or we have fetched every requested item.
          bool success = pageResult.Result;
          bool fetchCompleted = ( itemsCountToSkip == 0 && itemsCountToTake == 0 );
          if( !success || fetchCompleted )
          {
            endPosition = pageResult.EndPositionObject;
            endPositionOffset = pageResult.EndPositionOffset;

            if( fetchCompleted )
            {
              bool hasFetchedTooMuch = ( fetchedItems != null && fetchedItems.Any() );
              bool isExtraItemOutOfBound = ( !success )
                                        && ( fetchedItems != null )
                                        && ( currentItems.Count > 0 )
                                        && ( !hasFetchedTooMuch );

              // We have fetched more items than necessary.  The end position
              // we have now isn't the one we would have if we weren't emulating
              // the move with a fetch.
              if( hasFetchedTooMuch || isExtraItemOutOfBound )
              {
                success = true;

                T endItem;
                int offset;

                if( currentItems.Count > 0 )
                {
                  endItem = currentItems.Last();
                  offset = ( !isExtraItemOutOfBound )
                             ? fetchedItems.Count() * unit
                             : unit;
                }
                else
                {
                  endItem = fetchedItems.First();
                  offset = ( !isExtraItemOutOfBound )
                             ? ( fetchedItems.Count() - 1 ) * unit
                             : unit;
                }

                endPosition = new ItemPosition( endItem );

                // Adjust the end position offset.
                if( endPositionOffset != null )
                {
                  if( endPositionOffset.ReferencePosition == ReferencePoint.BeforeFirst )
                  {
                    endPositionOffset -= offset;
                  }
                  else
                  {
                    endPositionOffset += offset;
                  }
                }
              }
            }

            ICollection<T> requestedItems = ( success || currentItems.Count > 0 )
                                              ? currentItems
                                              : null;

            return new DependentCallResult(
                     new JumpMoveAndFetchResult<T>(
                       ( JumpMoveAndFetchAsyncResult )par,
                       actualMoveCount,
                       requestedItems,
                       success,
                       endPosition,
                       endPositionOffset,
                       totalItemsCount ) );
          }
          else
          {
            // We need to fetch another page.
            sourcePosition = pageResult.EndPositionObject;
            isFirstPage = false;

            return new DependentCallResult( null, getPage, mergePage );
          }
        };

      JumpMoveAndFetchAsyncResult asyncResult = new JumpMoveAndFetchAsyncResult(
                                                  FetchPagingEnumerator<T>.GetUnidirectionalPagesCallId,
                                                  callback,
                                                  state,
                                                  parameters );

      asyncResult.Start( getPage, mergePage );

      return asyncResult;
    }

    private DependentCallResult EndGetUnidirectionalPages(
      JumpMoveAndFetchAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( FetchPagingEnumerator<T>.GetUnidirectionalPagesCallId );

      return new DependentCallResult( asyncResult.GetCallResult<JumpMoveAndFetchResult<T>>() );
    }

    private bool HandleUnidirectionalPages(
      JumpMoveAndFetchAsyncResult asyncResult,
      JumpMoveAndFetchParameters parameters )
    {
      long moveCount = parameters.ExpectedMoveCount;
      int fetchCount = parameters.ExpectedFetchCount;

      // Make sure the query is unidirectional.
      if( moveCount != 0
        && fetchCount != 0
        && DataUtils.IsQueryingForward( moveCount, fetchCount ) != DataUtils.IsQueryingForward( 0, fetchCount ) )
        return false;

      // This anonymous method will fetch a data page.
      Func<AsyncCallback, object, IAsyncResult> getPages =
        ( cb, st ) => this.BeginGetUnidirectionalPages( parameters, cb, st );

      // This anonymous method will ask for more pages if necessary.
      Func<AsyncResult, IAsyncResult, DependentCallResult> mergePages =
        ( par, iar ) => this.EndGetUnidirectionalPages( ( JumpMoveAndFetchAsyncResult )iar );

      asyncResult.Start( getPages, mergePages );

      return true;
    }

    private JumpMoveAndFetchAsyncResult BeginGetBidirectionalPages(
      JumpMoveAndFetchParameters parameters,
      AsyncCallback callback,
      object state )
    {
      long itemsCountToSkip = parameters.ExpectedMoveCount;
      int itemsCountToTake = parameters.ExpectedFetchCount;

      // Make sure the query is bidirectional.
      Debug.Assert( ( itemsCountToSkip != 0 )
                 && ( itemsCountToTake != 0 )
                 && ( DataUtils.IsQueryingForward( itemsCountToSkip, itemsCountToTake ) != DataUtils.IsQueryingForward( 0, itemsCountToTake ) ) );

      // This anonymous method starts the query that is returning the items that
      // are in the move direction from the starting item.
      Func<AsyncCallback, object, IAsyncResult> getItems =
        ( cb, st ) =>
        {
          JumpMoveAndFetchParameters newParameters = new JumpMoveAndFetchParameters(
                                                       parameters.SourcePosition,
                                                       itemsCountToSkip,
                                                       Convert.ToInt32( -itemsCountToSkip ),
                                                       parameters.ApproximateMove,
                                                       parameters.Context );

          return this.BeginJumpMoveAndFetch( newParameters, cb, st );
        };

      // This anonymous method starts the query that is returning the items that
      // are in the fetch direction from the starting item.
      Func<AsyncResult, IAsyncResult, DependentCallResult> getRemainingItems =
        ( par, iar ) =>
        {
          JumpMoveAndFetchResult<T> moveResult = this.EndJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )iar );

          if( ( m_disposed ) || ( moveResult == null ) )
            return new DependentCallResult( null );

          if( !moveResult.Result )
            return new DependentCallResult(
                     new JumpMoveAndFetchResult<T>(
                       ( JumpMoveAndFetchAsyncResult )par,
                       moveResult.ActualMoveCount,
                       moveResult.FetchedItems,
                       moveResult.Result,
                       moveResult.EndPositionObject,
                       moveResult.EndPositionOffset,
                       moveResult.TotalCount ) );

          Func<AsyncCallback, object, IAsyncResult> getReverseItems =
            ( cb, st ) =>
            {
              JumpMoveAndFetchParameters newParameters = new JumpMoveAndFetchParameters(
                                                           parameters.SourcePosition,
                                                           0,
                                                           Convert.ToInt32( itemsCountToSkip + itemsCountToTake ),
                                                           parameters.ApproximateMove,
                                                           parameters.Context );

              return this.BeginJumpMoveAndFetch( newParameters, cb, st );
            };

          Func<AsyncResult, IAsyncResult, DependentCallResult> mergeItems =
            ( p, ar ) =>
            {
              JumpMoveAndFetchResult<T> fetchResult = this.EndJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )ar );

              if( ( m_disposed ) || ( fetchResult == null ) )
                return new DependentCallResult( null );

              List<T> currentItems = new List<T>( moveResult.FetchedItems );
              if( fetchResult.FetchedItems != null )
              {
                currentItems.AddRange( fetchResult.FetchedItems );
              }

              return new DependentCallResult(
                       new JumpMoveAndFetchResult<T>(
                         ( JumpMoveAndFetchAsyncResult )p,
                         moveResult.ActualMoveCount,
                         currentItems,
                         fetchResult.Result,
                         fetchResult.EndPositionObject,
                         fetchResult.EndPositionOffset,
                         fetchResult.TotalCount ) );
            };

          return new DependentCallResult( null, getReverseItems, mergeItems );
        };

      JumpMoveAndFetchAsyncResult asyncResult = new JumpMoveAndFetchAsyncResult(
                                                  FetchPagingEnumerator<T>.GetBidirectionalPagesCallId,
                                                  callback,
                                                  state,
                                                  parameters );

      asyncResult.Start( getItems, getRemainingItems );

      return asyncResult;
    }

    private DependentCallResult EndGetBidirectionalPages(
      JumpMoveAndFetchAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( FetchPagingEnumerator<T>.GetBidirectionalPagesCallId );

      return new DependentCallResult( asyncResult.GetCallResult<JumpMoveAndFetchResult<T>>() );
    }

    private bool HandleBidirectionalPages(
      JumpMoveAndFetchAsyncResult asyncResult,
      JumpMoveAndFetchParameters parameters )
    {
      long moveCount = parameters.ExpectedMoveCount;
      int fetchCount = parameters.ExpectedFetchCount;

      // Make sure the query is bidirectional.
      if( moveCount == 0
        || fetchCount == 0
        || DataUtils.IsQueryingForward( moveCount, fetchCount ) == DataUtils.IsQueryingForward( 0, fetchCount ) )
        return false;

      // Try to apply an optimized version of a bidirectional query.
      if( this.HandleBidirectionalReversedPages( asyncResult, parameters ) )
        return true;

      // This anonymous method will fetch a data page.
      Func<AsyncCallback, object, IAsyncResult> getPages =
        ( cb, st ) => this.BeginGetBidirectionalPages( parameters, cb, st );

      // This anonymous method will ask for more pages if necessary.
      Func<AsyncResult, IAsyncResult, DependentCallResult> mergePages =
        ( par, iar ) => this.EndGetBidirectionalPages( ( JumpMoveAndFetchAsyncResult )iar );

      asyncResult.Start( getPages, mergePages );

      return true;
    }

    private JumpMoveAndFetchAsyncResult BeginGetBidirectionalReversedPages(
      JumpMoveAndFetchParameters parameters,
      AsyncCallback callback,
      object state )
    {
      long itemsCountToSkip = parameters.ExpectedMoveCount;
      int itemsCountToTake = parameters.ExpectedFetchCount;

      // Make sure the query is an optimizable bidirectional query.
      Debug.Assert( ( itemsCountToSkip != 0 )
                 && ( itemsCountToTake != 0 )
                 && ( DataUtils.IsQueryingForward( itemsCountToSkip, itemsCountToTake ) != DataUtils.IsQueryingForward( 0, itemsCountToTake ) )
                 && ( Math.Abs( itemsCountToSkip ) >= Math.Abs( itemsCountToTake ) - 1 ) );

      SourcePosition sourcePosition = parameters.SourcePosition;

      // Transform the query into a unidirectional query.
      itemsCountToSkip += itemsCountToTake + DataUtils.GetUnit( itemsCountToSkip );
      itemsCountToTake = -itemsCountToTake;

      bool failFromStart = false;
      SourcePosition endPosition = null;
      RelativeOffset endPositionOffset = null;

      // Since we are starting out of bound, we already know one item will be missing.
      if( ( itemsCountToSkip == 0 ) && ( sourcePosition.IsOutOfBound ) )
      {
        failFromStart = true;
        endPosition = sourcePosition;
        endPositionOffset = new RelativeOffset(
                              0,
                              ( sourcePosition.IsBeforeFirst )
                                ? ReferencePoint.BeforeFirst
                                : ReferencePoint.AfterLast );

        int unit = DataUtils.GetUnit( itemsCountToTake );
        itemsCountToSkip = unit;
        itemsCountToTake = itemsCountToTake - unit;
      }

      // This anonymous method starts the unidirectional query.
      Func<AsyncCallback, object, IAsyncResult> getData =
        ( cb, st ) =>
        {
          JumpMoveAndFetchParameters newParameters = new JumpMoveAndFetchParameters(
                                                       sourcePosition,
                                                       itemsCountToSkip,
                                                       itemsCountToTake,
                                                       parameters.ApproximateMove,
                                                       parameters.Context );

          return this.BeginJumpMoveAndFetch( newParameters, cb, st );
        };

      // This anonymous method reverse the result set.
      Func<AsyncResult, IAsyncResult, DependentCallResult> reverseData =
        ( par, iar ) =>
        {
          JumpMoveAndFetchResult<T> result = this.EndJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )iar );

          if( ( m_disposed ) || ( result == null ) )
            return new DependentCallResult( null );

          bool success = !failFromStart && result.Result;
          Nullable<long> actualMoveCount = result.ActualMoveCount;
          ICollection<T> fetchedItems = result.FetchedItems;
          EstimateInt64 totalItemsCount = result.TotalCount;

          // We were able to move to the appropriate location.
          if( result.Result )
          {
            // Reverse the fetched items.
            List<T> currentItems = new List<T>( fetchedItems.Reverse() );
            fetchedItems = currentItems;

            // Find out the real actual move count.
            if( actualMoveCount.HasValue )
            {
              actualMoveCount = actualMoveCount.Value + result.GetFetchDisplacement();
            }

            if( !failFromStart )
            {
              endPosition = result.EndPositionObject;
              endPositionOffset = result.EndPositionOffset;

              // Find out the real end position.
              if( currentItems.Count > 1 )
              {
                endPosition = new ItemPosition( currentItems.Last() );

                if( endPositionOffset != null )
                {
                  long offset = ( currentItems.Count - 1 ) * -result.GetFetchUnit();

                  if( endPositionOffset.ReferencePosition == ReferencePoint.BeforeFirst )
                  {
                    endPositionOffset += offset;
                  }
                  else
                  {
                    endPositionOffset -= offset;
                  }
                }
              }
            }
          }
          // The move went beyond the end of the source.
          else
          {
            // Find out the real actual move count.
            if( actualMoveCount.HasValue )
            {
              ItemPosition startPosition = result.SourcePosition as ItemPosition;

              // We must adjust the move count if the starting item position wasn't in the source.
              if( ( actualMoveCount == 0 )
                && ( startPosition != null )
                && ( ( ( fetchedItems != null ) && ( fetchedItems.Any() ) && ( !object.Equals( startPosition.Item, fetchedItems.First() ) ) )
                  || ( ( fetchedItems == null ) && ( result.EndPositionObject.IsOutOfBound ) ) ) )
              {
                actualMoveCount = result.GetFetchUnit();
              }

              if( fetchedItems != null )
              {
                actualMoveCount = actualMoveCount.Value + fetchedItems.Count() * result.GetFetchUnit();
              }
            }

            // Find out if we are before or after the end of source.
            if( result.ExpectedFetchCount > 0 )
            {
              endPosition = SourcePosition.AfterLast;
              endPositionOffset = new RelativeOffset( 0, ReferencePoint.AfterLast );
            }
            else
            {
              endPosition = SourcePosition.BeforeFirst;
              endPositionOffset = new RelativeOffset( 0, ReferencePoint.BeforeFirst );
            }

            // Since we went beyond the end of the source, the fetch part shouldn't have
            // occured.  We must not return any items.
            fetchedItems = null;
          }

          return new DependentCallResult(
                   new JumpMoveAndFetchResult<T>(
                     ( JumpMoveAndFetchAsyncResult )par,
                     actualMoveCount,
                     fetchedItems,
                     success,
                     endPosition,
                     endPositionOffset,
                     totalItemsCount ) );
        };

      JumpMoveAndFetchAsyncResult asyncResult = new JumpMoveAndFetchAsyncResult(
                                                  FetchPagingEnumerator<T>.GetBidirectionalReversedPagesCallId,
                                                  callback,
                                                  state,
                                                  parameters );

      asyncResult.Start( getData, reverseData );

      return asyncResult;
    }

    private DependentCallResult EndGetBidirectionalReversedPages(
      JumpMoveAndFetchAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( FetchPagingEnumerator<T>.GetBidirectionalReversedPagesCallId );

      return new DependentCallResult( asyncResult.GetCallResult<JumpMoveAndFetchResult<T>>() );
    }

    private bool HandleBidirectionalReversedPages(
      JumpMoveAndFetchAsyncResult asyncResult,
      JumpMoveAndFetchParameters parameters )
    {
      long moveCount = parameters.ExpectedMoveCount;
      int fetchCount = parameters.ExpectedFetchCount;

      // Make sure the query is bidirectional, but can be emulated as unidirectional.
      if( moveCount == 0
        || fetchCount == 0
        || DataUtils.IsQueryingForward( moveCount, fetchCount ) == DataUtils.IsQueryingForward( 0, fetchCount )
        || Math.Abs( moveCount ) < Math.Abs( fetchCount ) - 1 )
        return false;

      // This anonymous method will fetch a data page.
      Func<AsyncCallback, object, IAsyncResult> getPages =
        ( cb, st ) => this.BeginGetBidirectionalReversedPages( parameters, cb, st );

      // This anonymous method will ask for more pages if necessary.
      Func<AsyncResult, IAsyncResult, DependentCallResult> mergePages =
        ( par, iar ) => this.EndGetBidirectionalReversedPages( ( JumpMoveAndFetchAsyncResult )iar );

      asyncResult.Start( getPages, mergePages );

      return true;
    }

    #region Private Fields

    private bool m_disposed; //false

    #endregion

    #region Protected Page Nested Type

    protected struct Page
    {
      internal Page( SourcePosition sourcePosition, long moveCount, long implicitMoveCount )
      {
        if( sourcePosition == null )
          throw new ArgumentNullException( "sourcePosition" );

        this.SourcePosition = sourcePosition;
        this.MoveCount = moveCount;
        this.ImplicitMoveCount = implicitMoveCount;
      }

      internal readonly SourcePosition SourcePosition;
      internal readonly long MoveCount;
      internal readonly long ImplicitMoveCount;
    }

    #endregion
  }
}
